/**
@author		Jong Min, Lee
@date		  02/06/2014
@brief		toolbox class

copyright(c) Tae Young Soft. All right reserved. 2014
*/
#include "stdafx.h"
#include "Taurus_Viewer/Toolbox.h"
#include <algorithm>
#include <vector>
#include "Taurus_ToolData/ToolboxData.h"
#include "Taurus_Util/Errors.h"
#include "Taurus_Viewer/ToolButton.h"
#include "Taurus_Viewer/TBLayoutInfo.h"

IMPLEMENT_DYNAMIC(CToolbox, CWnd)

CToolbox::CToolbox()
: m_enumResType(MonitorResolution_Normal)
, m_bEnableNotification(TRUE)
, m_bMuseRearrange(FALSE)
, m_bIsRearrangeLocked(FALSE)
{}

CToolbox::~CToolbox()
{
  removeAllTools();
}

DWORD CToolbox::getToolIndex(const CPoint& pt, unsigned& index) const
{
  CRect rcToolBtn;
  unsigned nFoundIndex = 0;
  for(auto pos = m_listToolButtons.cbegin(); 
      pos != m_listToolButtons.cend();
      ++pos, ++nFoundIndex)
  {
    (*pos)->GetWindowRect(&rcToolBtn);
    if(rcToolBtn.PtInRect(pt))
    {
      index = nFoundIndex;
      return ERROR_SUCCESS;
    }
  }
  return ERROR_OBJECT_NOT_FOUND;
}

DWORD CToolbox::insertToolAt(const unsigned index, const CToolData* pToolData)
{
  if(index >= m_listToolButtons.size() || pToolData == NULL)
    return ERROR_INVALID_PARAMETER;

  //allocate memory for tool button
  CToolButton* pNewToolBtn = NULL;
  _TRY_ALLOC(pNewToolBtn, CToolButton)
  if(pNewToolBtn == NULL)
    return ERROR_NOT_ENOUGH_MEMORY;

  pNewToolBtn->attachToolData(pToolData);

  if(::IsWindow(this->GetSafeHwnd()))
  {
    DWORD dwError = createToolButton(pNewToolBtn);
    if(dwError != ERROR_SUCCESS)
    {
      _DELETE(pNewToolBtn);
      return dwError;
    }
  }

  auto pos = m_listToolButtons.begin();
  std::advance(pos, index);
  _TRY_BEGIN
  m_listToolButtons.insert(pos, pNewToolBtn);
  _CATCH_ALL
  _DELETE_WND(pNewToolBtn);
  return ERROR_NOT_ENOUGH_MEMORY;
  _CATCH_END

  if(::IsWindow(this->GetSafeHwnd()))
  {
    //if the rearrange operation is not locked, rearrange buttons and redraw
    if(!m_bIsRearrangeLocked)
    {
      rearrangeButtons();
      Invalidate();
    }
    else
    {
      m_bMuseRearrange = TRUE;
    }
  }
  
  return ERROR_SUCCESS;
}

DWORD CToolbox::addTool(const CToolData* pToolData)
{
  if(pToolData == NULL)
    return ERROR_INVALID_PARAMETER;

  //allocate for new tool button
  CToolButton* pNewToolBtn = NULL;
  _TRY_ALLOC(pNewToolBtn, CToolButton)
  if(pNewToolBtn == NULL)
    return ERROR_NOT_ENOUGH_MEMORY;

  pNewToolBtn->attachToolData(pToolData);

  BOOL bThisIsWindow = (::IsWindow(this->GetSafeHwnd()));
  if(bThisIsWindow)
  {
    DWORD dwError = createToolButton(pNewToolBtn);
    if(dwError != ERROR_SUCCESS)
    {
      _DELETE(pNewToolBtn);
      return dwError;
    }
  }

  _TRY_BEGIN
  m_listToolButtons.push_back(pNewToolBtn);
  _CATCH_ALL
  _DELETE_WND(pNewToolBtn);
  return ERROR_NOT_ENOUGH_MEMORY;
  _CATCH_END

  if(bThisIsWindow)
  {
    //if the rearrange operation is not locked, then rearrange and redraw 
    if(!m_bIsRearrangeLocked)
    {
      rearrangeButtons();
      Invalidate();
    }
    else
    {
      m_bMuseRearrange = TRUE;
    }
  }

  return ERROR_SUCCESS;
}

DWORD CToolbox::openToolbox(const CToolboxData* pToolboxData)
{
  if(pToolboxData == NULL)
    return ERROR_INVALID_PARAMETER;

  removeAllTools();

  BOOL bIsThisWindow = ::IsWindow(this->GetSafeHwnd());

  unsigned nNumTools = pToolboxData->getNumToolData();
  if(nNumTools == 0)
  {
    m_bMuseRearrange = FALSE;

    if(bIsThisWindow)
    {
      if(!m_bIsRearrangeLocked)
        Invalidate();
    }
    return ERROR_SUCCESS;
  }

  DWORD dwError = ERROR_SUCCESS;
  pToolboxData->forEachToolData(
    [this, &bIsThisWindow, &dwError](const CToolData* pToolData)
  {
    CToolButton* pNewToolBtn = NULL;
    _TRY_ALLOC(pNewToolBtn, CToolButton)
    if(pNewToolBtn == NULL)
    {
      dwError = ERROR_NOT_ENOUGH_MEMORY;
      return false;
    }

    pNewToolBtn->attachToolData(pToolData);

    if(bIsThisWindow)
    {
      dwError = this->createToolButton(pNewToolBtn);
      if(dwError != ERROR_SUCCESS)
      {
        _DELETE(pNewToolBtn);
        return false;
      }
    }

    _TRY_BEGIN
    this->m_listToolButtons.push_back(pNewToolBtn);
    _CATCH_ALL
    _DELETE_WND(pNewToolBtn);
    dwError = ERROR_NOT_ENOUGH_MEMORY;
    return false;
    _CATCH_END
    return true;
  });

  if(dwError != ERROR_SUCCESS)
  {
    removeAllTools();
    return dwError;
  }

  if(bIsThisWindow)
  {
    if(!m_bIsRearrangeLocked)
    {
      rearrangeButtons();
      Invalidate();
    }
    else
    {
      m_bMuseRearrange = TRUE;
    }
  }

  return ERROR_SUCCESS;
}

DWORD CToolbox::selectTool(UINT uID)
{
  //TODO(Jong Min, Lee): find a tool button by using ID and make it selected.
  return ERROR_NOT_SUPPORTED;
}

DWORD CToolbox::enableTool(UINT uID, BOOL bEnable)
{
  auto pos = find_if(m_listToolButtons.begin(), m_listToolButtons.end(),
    [&uID](CToolButton* pToolBtn)
  {
    return (::IsWindow(pToolBtn->GetSafeHwnd())) && pToolBtn->GetDlgCtrlID() == uID;
  });

  if(pos == m_listToolButtons.end())
    return ERROR_OBJECT_NOT_FOUND;

  (*pos)->EnableWindow(bEnable);
  return ERROR_SUCCESS;
}

void CToolbox::enableAllTools(BOOL bEnable)
{
  for_each(m_listToolButtons.begin(), m_listToolButtons.end(),
    [&bEnable](CToolButton* pToolBtn)
  {
    if(::IsWindow(pToolBtn->GetSafeHwnd()))
      pToolBtn->EnableWindow(bEnable);
  });
}

DWORD CToolbox::removeTool(unsigned index)
{
  if(index >= m_listToolButtons.size())
    return ERROR_INVALID_PARAMETER;

  auto pos = m_listToolButtons.begin();
  std::advance(pos, index);
  _DELETE_WND(*pos);
  m_listToolButtons.erase(pos);

  if(::IsWindow(this->GetSafeHwnd()))
  {
    if(!m_bIsRearrangeLocked)
    {
      rearrangeButtons();
      Invalidate();
    }
    else
    {
      m_bMuseRearrange = TRUE;
    }
  }

  return ERROR_SUCCESS;
}

void CToolbox::removeAllTools()
{
  for(auto pos = m_listToolButtons.begin(), e = m_listToolButtons.end();
      pos != e;
      ++pos)
  {
    _DELETE_WND(*pos);
  }
  m_listToolButtons.clear();

  if(::IsWindow(this->GetSafeHwnd()))
  {
    if(!m_bIsRearrangeLocked)
    {
      rearrangeButtons();
      Invalidate();
    }
    else
    {
      m_bMuseRearrange = TRUE;
    }
  }
}

void CToolbox::lockRearrange()
{
  m_bIsRearrangeLocked = TRUE;
}

void CToolbox::releaseRearrange()
{
  m_bIsRearrangeLocked = FALSE;
  if(m_bMuseRearrange)
    rearrangeButtons();
}

void CToolbox::rearrangeButtons()
{
  CRect rcClient;
  GetClientRect(&rcClient);
  if(rcClient.IsRectEmpty())
    return;

  BOOL bVertical = rcClient.Height() > rcClient.Width();
  if(bVertical)
    rearrangeButtonsV();
  else
    rearrangeButtonsH();

  m_bMuseRearrange = FALSE;
}

void CToolbox::rearrangeButtonsV()
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin;
  getToolButtonMargin(m_enumResType, TRUE, csMargin);

  CSize csBtnSize;
  getToolButtonSize(m_enumResType, TRUE, csBtnSize);

  CPoint ptTl(csMargin.cx, csMargin.cy);
  CPoint ptBtnTl = ptTl;
  unsigned index = 0;
  for(auto pos = m_listToolButtons.begin(), e = m_listToolButtons.end();
      pos != e;
      ++pos, ++index)
  {
    (*pos)->MoveWindow(CRect(ptBtnTl, csBtnSize), FALSE);
    if((index + 1) % V_TB_NUM_TOOL_BTN_COLS)
    {
      ptBtnTl.x += (csBtnSize.cx + csMargin.cx);
    }
    else
    {
      ptBtnTl.x = ptTl.x;
      ptBtnTl.y = (ptBtnTl.y + csBtnSize.cy + csMargin.cy);
    }
  }
}

void CToolbox::rearrangeButtonsH()
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin;
  getToolButtonMargin(m_enumResType, FALSE, csMargin);
 
  CSize csBtnSize;
  getToolButtonSize(m_enumResType, FALSE, csBtnSize);

  CPoint ptTl(csMargin.cx, csMargin.cy);
  CPoint ptBtnTl = ptTl;
  unsigned index = 0;
  for(auto pos = m_listToolButtons.begin(), e = m_listToolButtons.end();
      pos != e;
      ++pos, ++index)
  {
    (*pos)->MoveWindow(CRect(ptBtnTl, csBtnSize), FALSE);

    if((index + 1) % H_TB_NUM_TOOL_BTN_ROWS)
    {
      ptBtnTl.y += (csBtnSize.cy + csMargin.cy);
    }
    else
    {
      ptBtnTl.x += (csBtnSize.cx + csMargin.cx);
      ptBtnTl.y = ptTl.y;
    }
  }
}

void CToolbox::getToolButtonMargin(MonitorResolutionType enumResType, 
                                   BOOL bVertical, 
                                   CSize& csMargin)
{
  if(bVertical)
  {
    switch(enumResType)
    {
    case MonitorResolution_Normal:
      {
        csMargin.cx = V_TB_TOOL_BTN_LEFT_MARGIN_NORMAL;
        csMargin.cy = V_TB_TOOL_BTN_TOP_MARGIN_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csMargin.cx = V_TB_TOOL_BTN_LEFT_MARGIN_3M;
        csMargin.cy = V_TB_TOOL_BTN_TOP_MARGIN_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csMargin.cx = V_TB_TOOL_BTN_LEFT_MARGIN_5M;
        csMargin.cy = V_TB_TOOL_BTN_TOP_MARGIN_5M;
      }
      break;
    }
  }//if(bVertical)
  else
  {
    switch(enumResType)
    {
    case MonitorResolution_Normal:
      {
        csMargin.cx = H_TB_TOOL_BTN_LEFT_MARGIN_NORMAL;
        csMargin.cy = H_TB_TOOL_BTN_TOP_MARGIN_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csMargin.cx = H_TB_TOOL_BTN_LEFT_MARGIN_3M;
        csMargin.cy = H_TB_TOOL_BTN_TOP_MARGIN_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csMargin.cx = H_TB_TOOL_BTN_LEFT_MARGIN_5M;
        csMargin.cy = H_TB_TOOL_BTN_TOP_MARGIN_5M;
      }
      break;
    }
  }
}

void CToolbox::getToolButtonSize(MonitorResolutionType enumResType, 
                                 BOOL bVertical, 
                                 CSize& csSize)
{
  if(bVertical)
  {
    switch(enumResType)
    {
    case MonitorResolution_Normal:
      {
        csSize.cx = V_TB_TOOL_BTN_WIDTH_NORMAL;
        csSize.cy = V_TB_TOOL_BTN_HEIGHT_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csSize.cx = V_TB_TOOL_BTN_WIDTH_3M;
        csSize.cy = V_TB_TOOL_BTN_HEIGHT_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csSize.cx = V_TB_TOOL_BTN_WIDTH_5M;
        csSize.cy = V_TB_TOOL_BTN_HEIGHT_5M;
      }
      break;
    }
  }//if(bVertical)
  else
  {
    switch(enumResType)
    {
    case MonitorResolution_Normal:
      {
        csSize.cx = H_TB_TOOL_BTN_WIDTH_NORMAL;
        csSize.cy = H_TB_TOOL_BTN_HEIGHT_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csSize.cx = H_TB_TOOL_BTN_WIDTH_3M;
        csSize.cy = H_TB_TOOL_BTN_HEIGHT_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csSize.cx = H_TB_TOOL_BTN_WIDTH_5M;
        csSize.cy = H_TB_TOOL_BTN_HEIGHT_5M;
      }
      break;
    }
  }
}

DWORD CToolbox::createToolButton(CToolButton* pToolButton)
{
  const CToolData* pToolData = pToolButton->getAttachedToolData();
  DWORD dwStyle = BS_ICON | BS_VCENTER | WS_VISIBLE |
                  WS_CHILD | BS_PUSHBUTTON | BS_OWNERDRAW;
  if(!pToolButton->Create(pToolData->strName,
                          dwStyle,
                          CRect(0, 0, 0, 0),
                          this,
                          pToolData->uID))
  {
    DWORD dwError = ::GetLastError();
    dwError = dwError == ERROR_SUCCESS ? ERROR_UNKNOWN : dwError;
    return dwError;
  }

  pToolButton->SetToolTipText(pToolData->strName);
  
  return ERROR_SUCCESS;
}

DWORD CToolbox::createAllToolButtons()
{
  destroyAllToolButtons();
  DWORD dwError = ERROR_SUCCESS;
  DWORD dwStyle = BS_ICON | BS_VCENTER | WS_VISIBLE |
                  WS_CHILD | BS_PUSHBUTTON | BS_OWNERDRAW;
  for(auto pos = m_listToolButtons.begin(), e = m_listToolButtons.end();
      pos != e;
      ++pos)
  {
    CToolButton* pToolBtn = (*pos);
    const CToolData* pToolData = pToolBtn->getAttachedToolData();
    if(!pToolBtn->Create(pToolData->strName,
                         dwStyle,
                         CRect(0, 0, 0, 0),
                         this,
                         pToolData->uID))
    {
      dwError = ::GetLastError();
      dwError = dwError == ERROR_SUCCESS ? ERROR_UNKNOWN : dwError;
      break;
    }

    pToolBtn->SetToolTipText(pToolData->strName);
  }

  if(dwError != ERROR_SUCCESS)
    destroyAllToolButtons();

  return dwError;
}

void CToolbox::destroyAllToolButtons()
{
  for(auto pos = m_listToolButtons.begin(), e = m_listToolButtons.end();
      pos != e;
      ++pos)
  {
    CToolButton* pToolBtn = (*pos);
    if(::IsWindow(pToolBtn->GetSafeHwnd()))
      pToolBtn->DestroyWindow();
  }
}

BOOL CToolbox::validateCreateParams(const CreateParams* pParams) const
{
  if(pParams == NULL)
    return FALSE;

  if(pParams->enumResType != MonitorResolution_Normal &&
     pParams->enumResType != MonitorResolution_3M     &&
     pParams->enumResType != MonitorResolution_5M)
  {
    return FALSE;
  }

  return TRUE;
}

BEGIN_MESSAGE_MAP(CToolbox, CWnd)
  ON_WM_CREATE()
  ON_WM_PAINT()
  ON_WM_ERASEBKGND()
  ON_WM_CREATE()
  ON_WM_SIZE()
END_MESSAGE_MAP()

int CToolbox::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if(CWnd::OnCreate(lpCreateStruct) == -1)
    return -1;

  CreateParams* pParams = reinterpret_cast<CreateParams*>(lpCreateStruct->lpCreateParams);
  if(!validateCreateParams(pParams))
    return -1;

  if(createAllToolButtons() != ERROR_SUCCESS)
    return -1;

  m_enumResType = pParams->enumResType;
  m_bEnableNotification = pParams->bEnableNotification;

  return 0;
}

void CToolbox::OnPaint()
{
  CPaintDC dc(this);

  //TODO(Jong Min, Lee): get toolbox bk color property
  COLORREF clrBk = RGB(45, 45, 46);
  CRect rcClient;
  GetClientRect(&rcClient);
  dc.FillSolidRect(rcClient, clrBk);
}

BOOL CToolbox::OnEraseBkgnd(CDC* pDC)
{
  UNUSED_ALWAYS(pDC);

  return TRUE;
}

void CToolbox::OnSize(UINT nType, int cx, int cy)
{
  CWnd::OnSize(nType, cx, cy);

  if(cx > 0 && cy > 0)
  {
    if(!m_bIsRearrangeLocked)
      rearrangeButtons();
    else
      m_bMuseRearrange = TRUE;
  }
}

BOOL CToolbox::OnCommand(WPARAM wParam, LPARAM lParam)
{
  BOOL bRet = CWnd::OnCommand(wParam, lParam);

  int nCode = HIWORD(wParam);

  if(m_bEnableNotification && nCode == BN_CLICKED)
  {
    UINT uID = LOWORD(wParam);
    HWND hwnd = (HWND)lParam;
    CToolButton* pToolBtn = reinterpret_cast<CToolButton*>(CWnd::FromHandle(hwnd));
    if(pToolBtn != NULL)
    {
      const CToolData* pToolData = pToolBtn->getAttachedToolData();
      //TODO(Jong Min, Lee):  notify a tool button is clicked
      if(pToolData->bNeedMouseInput)
        selectTool(uID);
    }
  }
  return bRet;
}